#ifndef BODY_H
#define BODY_H

#include "widgets/menu.h"
#include "utils.h"
#include "features/feature.h"

class BodyListener
{
public:
    virtual void OnBodyAttach(Menu* m) = 0;
    virtual void OnBodyDetach(Menu* m) = 0;
    virtual void OnBodyHidden() = 0;
};

class Body: public GuiBase, public AnimationObserver, public Observable< BodyListener >,
            public WidgetListener
{
public:
    typedef vector< BodyListener* > Listeners;

    Body(Engine* _engine, int _x, int _y, int _bx, int _by)
        :GuiBase(_engine, _x, _y), bodyAnime(engine->fps, 0), bodyDetachAnime(engine->fps, 300),
        bx(_bx), by(_by), visible(false),
        endPoint1(0, 0), endPoint2(0, 0)

    {
        w = bx - x;
        h = by - y;
        sx -= 110;
        sw = w + 115;
        sh = h + 5;
        invalidated = false;
        isAttached = false;

        attaching = false;
        detaching = false;
        attached = 0;
        currentFeature = 0;

        // show/hide animation
        m2BLength = leftUp = topRight = rightDown = downLeft = downUp = b2MLength = tlArc = trArc = brArc = blArc = muArc = mdArc = 0;
        int* v[13] = {&m2BLength, &leftUp, &topRight, &rightDown, &downLeft, &downUp, &b2MLength, &tlArc, &trArc, &brArc, &blArc, &muArc, &mdArc};
        string names[13] = {"m2b", "lUp", "tRight", "rDown", "dLeft", "dUp", "b2m", "tlArc", "trArc", "brArc", "blArc", "muArc", "mdArc"};
        g.AddSimpleProperties(names, v, 13, &invalidated);
        for(int i=0; i<13; i++)
        {
            i_id[i] = g.AddInterpolator(bodyAnime.GetTime(), 0, 0, names[i], 0, (i<7)? 0 : 90);
        }

        // detach animation
        m2bDetachLength = arcDetach = vertFill = 0;
        detachGraph.AddSimpleProperty("m2b_length", m2bDetachLength, &invalidated);
        detachGraph.AddSimpleProperty("arc_detach", arcDetach, &invalidated);
        detachGraph.AddSimpleProperty("vert_fill", vertFill, &invalidated);
        m2bDetachLength_id = detachGraph.AddInterpolator(bodyDetachAnime.GetTime(), 0, 100, "m2b_length", 0, 0);
        arcDetach_id = detachGraph.AddInterpolator(bodyDetachAnime.GetTime(), 100, 200, "arc_detach", 0, 90);
        detachGraph.AddInterpolator(bodyDetachAnime.GetTime(), 200, 300, "vert_fill", 0, 30);

        // animations registration
        bodyAnime.AddObserver(this);
        engine->AddAnimation(&bodyAnime);
        bodyDetachAnime.AddObserver(this);
        engine->AddAnimation(&bodyDetachAnime);
    }

    virtual ~Body(){}

    int GetWidth(){ return w; }
    int GetHeight(){ return h; }

    void Hide()
    {
        if(!visible) return;
        detaching = false;
        attaching = false;
        currentFeature->Hide();
    }

    void Show(Menu* target, Feature* toShow)
    {
        if(visible) return;
        visible = true;

        currentFeature = toShow;
        if(toShow->GetObserversCount() == 0)
            toShow->AddObserver(this);

        GetMenuEndpoints(target);
        attaching = true;
        attached = target;
        sx = endPoint1.x();
        sw = x + w - sx + 10;

        bodyAnime.Stop();
        int animeDuration = 1000;
        bodyAnime.SetDuration(animeDuration);
        g.Get(i_id[0])->SetMaxY(x - endPoint1.x() - 20);    // menu to body line
        g.Get(i_id[1])->SetMaxY(endPoint1.y() - y - 40);    // left up line
        g.Get(i_id[2])->SetMaxY(w - 40);                    // horizontal top line
        g.Get(i_id[3])->SetMaxY(h - 40);                    // right down line
        g.Get(i_id[4])->SetMaxY(w - 40);                    // horizontal bottom line
        g.Get(i_id[5])->SetMaxY(y + h - endPoint2.y() - 40);// bottom up line
        g.Get(i_id[6])->SetMaxY(x - endPoint1.x() - 20);    // body to menu line

        int pSeq[13] = {0, 11, 1, 7, 2, 8, 3, 9, 4, 10, 5, 12, 6};
        int arcStep = animeDuration * 0.1 /6;
        int borderStep = animeDuration * 0.5 / 3;
        int linkStep = animeDuration * 0.15 / 2;
        int upDownStep = animeDuration * 0.25 / 2;
        int dur[13] = {linkStep, arcStep, upDownStep, arcStep, borderStep, arcStep, borderStep, arcStep, borderStep, arcStep, upDownStep, arcStep, linkStep};
        int curTime = 0;
        for(int i=0; i<12; i++)
        {
            g.Get(i_id[pSeq[i]])->SetMinX(curTime);
            g.Get(i_id[pSeq[i]])->SetMaxX(curTime + dur[i]);
            curTime += dur[i];
        }
        g.Get(i_id[pSeq[12]])->SetMinX(curTime);
        g.Get(i_id[pSeq[12]])->SetMaxX(animeDuration);
        bodyAnime.Play();
    }

    void AttachTo(Menu* m, Feature* toShow)
    {
        GetMenuEndpoints(m);
        attaching = true;

        currentFeature = toShow;
        if(toShow->GetObserversCount() == 0)
            toShow->AddObserver(this);

        attached = m;

        if(m->GetId() == 5)
        {
            detachGraph.Get(m2bDetachLength_id)->SetMaxY(m2BLength + 20);
            m2bDetachLength = m2BLength + 20;
        }

        g.Get(i_id[0])->SetMaxY(x - endPoint1.x() - 20);    // menu to body line
        g.Get(i_id[1])->SetMaxY(endPoint1.y() - y - 40);    // left up line
        g.Get(i_id[5])->SetMaxY(y + h - endPoint2.y() - 40);// bottom up line
        g.Get(i_id[6])->SetMaxY(x - endPoint1.x() - 20);    // body to menu line
        leftUp = endPoint1.y() - y - 40;

        m2BLength = x - endPoint1.x() - 20;
        b2MLength = x - endPoint1.x() - 20;
        downUp = y + h - endPoint2.y() - 40;
        bodyDetachAnime.Reverse(true);
        bodyDetachAnime.Play();
    }

    void Detach()
    {
        attaching = false;
        detaching = true;
        currentFeature->Hide();
    }

    bool IsVisible(){ return visible; }
    bool IsAttached(){ return isAttached; }

    // GuiBase implementation
    virtual void Draw(QPainter& painter)
    {
        if(!visible) return;

        painter.setPen(Qt::black);

        if(m2BLength - m2bDetachLength >=2)
        {
            painter.drawLine(endPoint1.x() + m2bDetachLength, endPoint1.y(), endPoint1.x() + m2BLength, endPoint1.y());
        }

        painter.drawArc(x - 40, endPoint1.y() - 40, 40, 40, (-90 + arcDetach) * 16, 16 * (muArc - arcDetach));

        if(leftUp>=2)
        {
            painter.drawLine(x, endPoint1.y() - 20 + vertFill, x, endPoint1.y() - 20 - leftUp);
        }

        painter.drawArc(x, y, 40, 40, -16 * 180, -16 * tlArc);

        if(topRight >= 2)
        {
            painter.drawLine(x + 20, y, x + 20 + topRight, y);
        }

        painter.drawArc(x + w - 40, y, 40, 40, 90 * 16, -16 * trArc);

        if(rightDown >= 2)
        {
            painter.drawLine(x + w, y + 20, x + w, y + 20 + rightDown);
        }

        painter.drawArc(x + w - 40, y + h - 40, 40, 40, 0, -16 * brArc);

        if(downLeft >= 2)
        {
            painter.drawLine(x + w - 20, y + h, x + w - 20 - downLeft, y + h);
        }

        painter.drawArc(x, y + h - 40, 40, 40, -16 * 90, -16 * blArc);

        if(downUp >= 2)
        {
            painter.drawLine(x, y + h - 20, x, y + h - 20 - downUp - vertFill);
        }

        painter.drawArc(x - 40, endPoint2.y(), 40, 40, 0, 16 * (mdArc - arcDetach));

        if(b2MLength - m2bDetachLength >= 2)
        {
            painter.drawLine(x - 20, endPoint2.y(), x - 20 - b2MLength + m2bDetachLength, endPoint2.y());
        }
        //invalidated = false;
    }

    // AnimationObserver implementation
    virtual void OnPause(Animation* a){};
    virtual void OnStop(Animation* a){};
    virtual void OnEnd(Animation* a)
    {
        if(a == &bodyAnime)
        {
            if(attaching)
            {   // the body is shown
                currentFeature->Show();
            } else
            {   // the body is hidden
                visible = false;
                isAttached = false;
                for(Listeners::iterator i=observers->begin(); i!=observers->end(); i++)
                {
                    (*i)->OnBodyHidden();
                }
            }
        } else if(a == &bodyDetachAnime)
        {
            if(bodyDetachAnime.IsReversed())
            {   // attaching to another menu
                currentFeature->Show();
            }else
            {   // detaching
                detaching = false;
                isAttached = false;
                for(Listeners::iterator i=observers->begin(); i!=observers->end(); i++)
                {
                    (*i)->OnBodyDetach(attached);
                }
            }
        }
    };
    virtual void OnPlay(Animation* a){};

    // FeatureListener implementation
    virtual void OnShow(Widget* w)
    {
        attaching = false;
        isAttached = true;
        visible = true;
        for(Listeners::iterator i=observers->begin(); i!=observers->end(); i++)
        {
            (*i)->OnBodyAttach(attached);
        }
    };

    virtual void OnHide(Widget* w)
    {
        currentFeature = 0;
        if(!detaching)
        {   // hiding
            bodyAnime.Reverse(true);
            bodyAnime.Play();
        }else
        {   // detaching
            detachGraph.Get(m2bDetachLength_id)->SetMaxY(m2BLength);
            bodyDetachAnime.Stop();
            bodyDetachAnime.Play();
        }
    };

private:
    Animation bodyAnime;
    Animation bodyDetachAnime;
    int bx, by;
    bool visible;
    Feature* currentFeature;

    // body show/hide properties
    int m2BLength, leftUp, topRight, rightDown, downLeft, downUp, b2MLength, tlArc, trArc, brArc, blArc, muArc, mdArc;
    int i_id[13];
    IntGraph g;

    // menu attach/detach flags
    Menu* attached;
    bool isAttached;
    bool attaching;

    // body detach properties
    bool detaching;
    int m2bDetachLength, m2bDetachLength_id;
    int arcDetach, arcDetach_id;
    int vertFill;
    IntGraph detachGraph;

    QPoint endPoint1;
    QPoint endPoint2;

    void GetMenuEndpoints(Menu* m)
    {
        endPoint1.setX(m->GetX() + 90);
        endPoint1.setY(m->GetY());
        endPoint2.setX(endPoint1.x());
        endPoint2.setY(endPoint1.y() + 20);
    }
};

#endif // BODY_H
